using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Text;

using WaveLib;

namespace WindowsApplication2
{
	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
		private System.Windows.Forms.Button buttonSave;
		private System.ComponentModel.IContainer components;
		private System.Windows.Forms.NotifyIcon notifyIcon1;
		private System.Windows.Forms.Button buttonPlay;
		private System.Windows.Forms.Button buttonPlayHigh;
		private System.Windows.Forms.Button buttonPlayLow;
		private System.Windows.Forms.Label label1;
		private System.Windows.Forms.Label label2;
		private System.Windows.Forms.Label label3;

		const int SAMPLERATE = 44100;
		const int CHANNELS = 2;
		const int BITSPERSAMPLE = 16;


		private System.Windows.Forms.Label label4;
		private System.Windows.Forms.Label label5;
		private System.Windows.Forms.Label label6;
		private System.Windows.Forms.Label label7;
		private System.Windows.Forms.Label label9;
		private System.Windows.Forms.Label label10;
		private System.Windows.Forms.Label label8;

		public Form1(string[] args)
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			if (args.Length != 0)
			{
				if (File.Exists(args[0]))
				{
					currentFile = args[0];
				}
			}

			// TEMP
			program = new byte[1000];
			for (int i = 0; i < program.Length; ++i)
			{
				//[7:6 block num][5:0 value]
				program[i] = (byte)((i & 63) | ((i / 512)<<6));
			}

			label4.Text = "uS per cycle: " + usPerCycle;
			label5.Text = "uS per half cycle: " + usPerHalfCycle;
			label6.Text = "uS per quarter cycle: " + usPerQuarterCycle;
			label7.Text = "uS per eighth cycle: " + usPerEighthCycle;
			label8.Text = "Samples per cycle: " + f1;
			label9.Text = "Bits per block pause: " + bpbp;
			label10.Text = "Baud rate = " + baudrate + " bits/second";
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.components = new System.ComponentModel.Container();
			this.buttonPlay = new System.Windows.Forms.Button();
			this.buttonSave = new System.Windows.Forms.Button();
			this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components);
			this.buttonPlayLow = new System.Windows.Forms.Button();
			this.buttonPlayHigh = new System.Windows.Forms.Button();
			this.label1 = new System.Windows.Forms.Label();
			this.label2 = new System.Windows.Forms.Label();
			this.label3 = new System.Windows.Forms.Label();
			this.label4 = new System.Windows.Forms.Label();
			this.label5 = new System.Windows.Forms.Label();
			this.label6 = new System.Windows.Forms.Label();
			this.label7 = new System.Windows.Forms.Label();
			this.label8 = new System.Windows.Forms.Label();
			this.label9 = new System.Windows.Forms.Label();
			this.label10 = new System.Windows.Forms.Label();
			this.SuspendLayout();
			// 
			// buttonPlay
			// 
			this.buttonPlay.Location = new System.Drawing.Point(8, 24);
			this.buttonPlay.Name = "buttonPlay";
			this.buttonPlay.Size = new System.Drawing.Size(72, 23);
			this.buttonPlay.TabIndex = 0;
			this.buttonPlay.Text = "Play";
			this.buttonPlay.Click += new System.EventHandler(this.buttonPlay_Click);
			// 
			// buttonSave
			// 
			this.buttonSave.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
			this.buttonSave.Location = new System.Drawing.Point(248, 24);
			this.buttonSave.Name = "buttonSave";
			this.buttonSave.Size = new System.Drawing.Size(72, 23);
			this.buttonSave.TabIndex = 1;
			this.buttonSave.Text = "Save";
			this.buttonSave.Click += new System.EventHandler(this.buttonSave_Click);
			// 
			// notifyIcon1
			// 
			this.notifyIcon1.Text = "notifyIcon1";
			this.notifyIcon1.Visible = true;
			// 
			// buttonPlayLow
			// 
			this.buttonPlayLow.Location = new System.Drawing.Point(88, 24);
			this.buttonPlayLow.Name = "buttonPlayLow";
			this.buttonPlayLow.Size = new System.Drawing.Size(72, 23);
			this.buttonPlayLow.TabIndex = 2;
			this.buttonPlayLow.Text = "Play";
			this.buttonPlayLow.Click += new System.EventHandler(this.buttonPlayLow_Click);
			// 
			// buttonPlayHigh
			// 
			this.buttonPlayHigh.Location = new System.Drawing.Point(168, 24);
			this.buttonPlayHigh.Name = "buttonPlayHigh";
			this.buttonPlayHigh.Size = new System.Drawing.Size(72, 23);
			this.buttonPlayHigh.TabIndex = 3;
			this.buttonPlayHigh.Text = "Play";
			this.buttonPlayHigh.Click += new System.EventHandler(this.buttonPlayHigh_Click);
			// 
			// label1
			// 
			this.label1.Location = new System.Drawing.Point(8, 8);
			this.label1.Name = "label1";
			this.label1.Size = new System.Drawing.Size(72, 16);
			this.label1.TabIndex = 4;
			this.label1.Text = "File";
			this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
			// 
			// label2
			// 
			this.label2.Location = new System.Drawing.Point(88, 8);
			this.label2.Name = "label2";
			this.label2.Size = new System.Drawing.Size(72, 16);
			this.label2.TabIndex = 5;
			this.label2.Text = "Low";
			this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
			// 
			// label3
			// 
			this.label3.Location = new System.Drawing.Point(168, 8);
			this.label3.Name = "label3";
			this.label3.Size = new System.Drawing.Size(72, 16);
			this.label3.TabIndex = 6;
			this.label3.Text = "High";
			this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
			// 
			// label4
			// 
			this.label4.Location = new System.Drawing.Point(8, 104);
			this.label4.Name = "label4";
			this.label4.Size = new System.Drawing.Size(152, 16);
			this.label4.TabIndex = 7;
			this.label4.Text = "label4";
			// 
			// label5
			// 
			this.label5.Location = new System.Drawing.Point(168, 104);
			this.label5.Name = "label5";
			this.label5.Size = new System.Drawing.Size(152, 16);
			this.label5.TabIndex = 8;
			this.label5.Text = "label5";
			// 
			// label6
			// 
			this.label6.Location = new System.Drawing.Point(8, 128);
			this.label6.Name = "label6";
			this.label6.Size = new System.Drawing.Size(152, 16);
			this.label6.TabIndex = 9;
			this.label6.Text = "label6";
			// 
			// label7
			// 
			this.label7.Location = new System.Drawing.Point(168, 128);
			this.label7.Name = "label7";
			this.label7.Size = new System.Drawing.Size(152, 16);
			this.label7.TabIndex = 10;
			this.label7.Text = "label7";
			// 
			// label8
			// 
			this.label8.Location = new System.Drawing.Point(8, 80);
			this.label8.Name = "label8";
			this.label8.Size = new System.Drawing.Size(152, 16);
			this.label8.TabIndex = 11;
			this.label8.Text = "label8";
			// 
			// label9
			// 
			this.label9.Location = new System.Drawing.Point(168, 80);
			this.label9.Name = "label9";
			this.label9.Size = new System.Drawing.Size(152, 16);
			this.label9.TabIndex = 12;
			this.label9.Text = "label9";
			// 
			// label10
			// 
			this.label10.Location = new System.Drawing.Point(8, 56);
			this.label10.Name = "label10";
			this.label10.Size = new System.Drawing.Size(152, 16);
			this.label10.TabIndex = 13;
			this.label10.Text = "label10";
			// 
			// Form1
			// 
			this.AllowDrop = true;
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(330, 154);
			this.Controls.Add(this.label10);
			this.Controls.Add(this.label9);
			this.Controls.Add(this.label8);
			this.Controls.Add(this.label7);
			this.Controls.Add(this.label6);
			this.Controls.Add(this.label5);
			this.Controls.Add(this.label4);
			this.Controls.Add(this.label3);
			this.Controls.Add(this.label2);
			this.Controls.Add(this.label1);
			this.Controls.Add(this.buttonPlayHigh);
			this.Controls.Add(this.buttonPlayLow);
			this.Controls.Add(this.buttonSave);
			this.Controls.Add(this.buttonPlay);
			this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
			this.Name = "Form1";
			this.Text = "[]SquareWave~";
			this.DragDrop += new System.Windows.Forms.DragEventHandler(this.Form1_DragDrop);
			this.Closed += new System.EventHandler(this.Form1_Closed);
			this.DragEnter += new System.Windows.Forms.DragEventHandler(this.Form1_DragEnter);
			this.ResumeLayout(false);

		}
		#endregion


		[STAThread]
		static void Main(string[] args) 
		{
			Application.Run(new Form1(args));
		}

		WaveLib.WaveOutPlayer m_player;

		Random numGen = new Random();

		string dt = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);

		byte[] program;

		const double maxLevel = (double)short.MaxValue * 1.0;
		const short maxLevelS = (short)maxLevel;
		const double minLevel = (double)short.MinValue * 1.0;
		const short minLevelS = (short)minLevel;

		const int f1 = 60;
		const int f1cyc = 1;
		const int f2 = f1 / 2;
		const int f2cyc = f1cyc * 2;

		const int usPerCycle = (int)(((double)f1 / 44100) * 1000000.0);
		const int usPerHalfCycle = (int)(((double)f1 / 44100) * 500000.0);
		const int usPerQuarterCycle = (int)(((double)f1 / 44100) * 250000.0);
		const int usPerEighthCycle = (int)(((double)f1 / 44100) * 125000.0);

		// pause per block is 26000 us = 26 milliseconds.
		//
		const int uSblockPause = 26000;

		// bits per block pause must be even multiple of 10.
		//
		const int rawBpbp = (uSblockPause / usPerCycle) + (((uSblockPause % usPerCycle) != 0) ? 1 : 0);
		const int bpbp = 10 * ((rawBpbp + 9) / 10);

		const int baudrate = (1000000 / usPerCycle);

		// 8 + stop + start;
		const int bitsPerFrame = 10;

		const int samplesPerBit = f1 * f1cyc;
		const int samplesPerFrame = bitsPerFrame * samplesPerBit;

		const int bufferSizeFrames = 20;
		const int bufferSizeBytes = samplesPerFrame * bufferSizeFrames;

		const int LOW = 0;
		const int HIGH = 1-LOW;
		const int LEADIN = LOW;
		const int STARTBIT = 1-LEADIN;
		const int STOPBIT = 1-STARTBIT;

		void outbit(int bit, byte[] bytes, ref int idx)
		{
			// default to '0'
			//
			int cycles = f1cyc;
			int samplesPerCycle = f1;

			if (bit != 0)
			{
				cycles = f2cyc;
				samplesPerCycle = f2;
			}

			for (int i = 0; i < cycles; ++i)
			{
				for (int j = 0; j < samplesPerCycle / 2; ++j)
				{
					// 1
					bytes[idx++] = maxLevelS & 0xff;
					bytes[idx++] = maxLevelS >> 8;
				}
				for (int j = 0; j < samplesPerCycle / 2; ++j)
				{
					// 0
					unchecked
					{
						byte x = (byte)(minLevelS & 0xff);
						bytes[idx++] = x;
						x = (byte)(minLevelS >> 8);
						bytes[idx++] = x;
					}
				}
			}
		}

		private enum opstate
		{
			starttone,
			blockpause,
			dataLen,
			data,
			nothingness
		};

		int pc;
		int startTone;
		opstate state;

		// returns true if more bytes to write
		//
		bool FillBuffer(byte[] bytes, int size, out int written)
		{
			int v, idx = 0;
			written = idx;

			// buffer is filled with words 
			while(idx < size)
			{
				switch(state)
				{
					case opstate.starttone:
						// start tone always fills a buffer
						while(idx < size)
						{
							outbit(LEADIN, bytes, ref idx);
						}

						written = idx;

						++startTone;
						if(startTone == 10)
						{
							state = opstate.dataLen;
						}
						break;

					case opstate.blockpause:
						outbit(LEADIN, bytes, ref idx);
						written = idx;

						++startTone;
						if(startTone == bpbp)
						{
							state = opstate.data;
						}
						break;

					case opstate.dataLen:
						// start tone always fills a buffer, so we don't need to check overruns

						// high part first
						v = program.Length / 256;

						outbit(STARTBIT, bytes, ref idx);
						for (int j = 0; j < 8; ++j)
						{
							outbit(v & 0x80, bytes, ref idx);
							v *= 2;
						}
						outbit(STOPBIT, bytes, ref idx);

						// then low
						v = program.Length & 255;

						outbit(STARTBIT, bytes, ref idx);
						for (int j = 0; j < 8; ++j)
						{
							outbit(v & 0x80, bytes, ref idx);
							v *= 2;
						}
						outbit(STOPBIT, bytes, ref idx);

						written = idx;
						
						state = opstate.data;

						break;

					case opstate.data:
						v = (int)program[pc];
						++pc;

						outbit(STARTBIT, bytes, ref idx);
						for (int j = 0; j < 8; ++j)
						{
							outbit(v & 0x80, bytes, ref idx);
							v *= 2;
						}
						outbit(STOPBIT, bytes, ref idx);
						written = idx;

						if (pc == program.Length)
						{
							state = opstate.nothingness;
						}

						if ((pc & 511) == 0)
						{
							startTone = 0;
							state = opstate.blockpause;
						}
						break;

					case opstate.nothingness:
						while (idx < size)
						{
							bytes[idx++] = 0;
							bytes[idx++] = 0;
						}
						break;
				}
			}

			return written != 0;
		}

		int empties;

		void MyFillFunc(IntPtr targetBuffer, int size)
		{
			byte[] bytes = new byte[size];

			if (m_player != null && pc < program.Length)
			{
				lock (this)
				{
					int dummy;
					FillBuffer(bytes, size, out dummy);
				}
				empties = 0;
			}
			else
			{
				++ empties;
				if (empties == 3)
				{
					buttonPlay.BeginInvoke(new ClickMeDelegate(ClickMe),null);
				}

				for (int i = 0; i < bytes.Length; i++)
				{
					bytes[i] = 0;
				}
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, targetBuffer, size);
		}

		private delegate void ClickMeDelegate();
		private void ClickMe()
		{
			buttonPlay_Click(null, null);
		}


		void MyLowFillFunc(IntPtr targetBuffer, int size)
		{
			int idx = 0;
			byte[] bytes = new byte[size];

			lock (this)
			{
				while(idx < size)
				{
					outbit(LOW, bytes, ref idx);
				}
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, targetBuffer, size);
		}

		void MyHighFillFunc(IntPtr targetBuffer, int size)
		{
			int idx = 0;
			byte[] bytes = new byte[size];

			lock (this)
			{
				while(idx < size)
				{
					outbit(HIGH, bytes, ref idx);
				}
			}

			System.Runtime.InteropServices.Marshal.Copy(bytes, 0, targetBuffer, size);
		}

		private void Form1_Closed(object sender, System.EventArgs e)
		{
			if (m_player != null)
			{
				lock (this)
				{
					m_player.Dispose();
					m_player = null;
				}
			}
	
		}



		// NASTY HACK FOR WRITING WAVS. PLEASE LOOK AWAY NOW. 

		private void CreateWaveFile(BinaryWriter werr)
		{
			char[] chars;

			chars = "RIFF".ToCharArray(0,4);
			werr.Write(chars,0,4);

			werr.Write((int)16);									// placeholder will be overwritten with total filesize - 4

			chars = "WAVE".ToCharArray(0,4);
			werr.Write(chars,0,4);

			chars = "fmt ".ToCharArray(0,4);
			werr.Write(chars,0,4);

			werr.Write((int)16);									// chunk data size

			werr.Write((short)1);									// compression code: pcm/uncompressed
			werr.Write((short)CHANNELS);							//

			werr.Write((int)SAMPLERATE);								//
			werr.Write((int)(SAMPLERATE*(BITSPERSAMPLE/8*CHANNELS)));	// ave bytes/sec

			werr.Write((short)(BITSPERSAMPLE/8*CHANNELS));			// block align
			werr.Write((short)BITSPERSAMPLE);						//

			chars = "data".ToCharArray(0,4);
			werr.Write(chars,0,4);

			werr.Write((int)0);										// placeholder, offset 40 - size of sample data in bytes
		}

		private void CloseWaveFile(BinaryWriter werr, int bytesWrit)
		{
			int size = bytesWrit;

			werr.Seek(40, SeekOrigin.Begin);
			werr.Write(size);

			werr.Seek(4, SeekOrigin.Begin);

			size += 36;
			werr.Write(size);
		}

		// OK IT'S SAFE TO LOOK AGAIN


		void LoadP()
		{
			// start from here
			//
			pc = 0;
			startTone = 0;
			state = opstate.starttone;

//////			using (BinaryReader br = new BinaryReader(File.OpenRead(currentFile)))
//////			{
//////				program = br.ReadBytes((int)br.BaseStream.Length);
//////			}
		}



		private void buttonPlay_Click(object sender, System.EventArgs e)
		{
			bool othersEnabled = Play(buttonPlay, new BufferFillEventHandler(MyFillFunc));
			buttonPlayLow.Enabled = othersEnabled;
			buttonPlayHigh.Enabled = othersEnabled;
			buttonSave.Enabled = othersEnabled;
		}


		private void buttonPlayLow_Click(object sender, System.EventArgs e)
		{
			bool othersEnabled = Play(buttonPlayLow, new BufferFillEventHandler(MyLowFillFunc));
			buttonPlay.Enabled = othersEnabled;
			buttonPlayHigh.Enabled = othersEnabled;
			buttonSave.Enabled = othersEnabled;
		}

		private void buttonPlayHigh_Click(object sender, System.EventArgs e)
		{
			bool othersEnabled = Play(buttonPlayHigh, new BufferFillEventHandler(MyHighFillFunc));
			buttonPlay.Enabled = othersEnabled;
			buttonPlayLow.Enabled = othersEnabled;
			buttonSave.Enabled = othersEnabled;
		}

		private bool Play(System.Windows.Forms.Button button, BufferFillEventHandler fillProc)
		{
			if (m_player != null)
			{
				lock (this)
				{
					m_player.Dispose();
					m_player = null;
				}
			}

			if (button.Text == "Stop")
			{
				button.Text = "Play";
			}
			else
			{
				LoadP();

				WaveLib.WaveFormat format = new WaveFormat(SAMPLERATE, 16, 1);
				m_player = new WaveLib.WaveOutPlayer(-1, format, bufferSizeBytes, 2, fillProc);

				button.Text = "Stop";
			}

			return button.Text == "Play";
		}


		private void buttonSave_Click(object sender, System.EventArgs e)
		{
			this.Enabled = false;

			if (m_player != null)
			{
				lock (this)
				{
					m_player.Dispose();
					m_player = null;
				}
			}

			LoadP();

			using (BinaryWriter wr = new BinaryWriter(File.OpenWrite("c:\\temp.wav")))
			{
				CreateWaveFile(wr);

				int bw = 0;
				int written = 0;
				byte[] buffer = new byte[bufferSizeBytes];
				byte[] buffer2 = new byte[bufferSizeBytes*2];

				// expand buffer to be stereo signal
				//
				while (FillBuffer(buffer, bufferSizeBytes, out written))
				{
					for (int i = 0; i < written; i += 2)
					{
						buffer2[i*2] = buffer[i];
						buffer2[i*2+1] = buffer[i+1];
						buffer2[i*2+2] = buffer[i];
						buffer2[i*2+3] = buffer[i+1];
					}

					wr.Write(buffer2, 0, written*2);
					bw += written*2;
				}

				CloseWaveFile(wr, bw);
			}

			this.Enabled = true;
		}



		private string currentFile;

		private void Form1_DragEnter(object sender, System.Windows.Forms.DragEventArgs e)
		{
			e.Effect = DragDropEffects.Copy;		
		}

		private void Form1_DragDrop(object sender, System.Windows.Forms.DragEventArgs e)
		{
			if( e.Data.GetDataPresent( DataFormats.FileDrop, false ) ) 
			{
				string[] names = (string[])e.Data.GetData( DataFormats.FileDrop );
				currentFile = names[0];
			}
		}

	}
}
